﻿////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// <copyright>Copyright 2008-2011 Andreas Huber Doenni</copyright>
// Distributed under the GNU General Public License version 2 (GPLv2).
// See accompanying file License.txt or copy at http://phuse.codeplex.com/license.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

namespace Phuse.Mime
{
    using System;
    using System.Diagnostics.CodeAnalysis;
    using System.IO;

    using Phuse.IO;

    /// <summary>Processes unstructured content with a given encoding transformation.</summary>
    /// <remarks>Unstructured content does not contain any structures that are relevant for MIME processing (e.g. header
    /// fields, body part boundaries). Examples for unstructured content are: text entity body, binary entity body,
    /// multipart preamble, multipart epilogue.</remarks>
    [SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable", Justification = "MemoryStream doesn't own any resources that need to be released. It is only disposable because other Stream subclasses need to be.")]
    internal abstract class UnstructuredContentProcessor
    {
        private readonly EncodingTransformation encodingTransformation;
        private MemoryStream content = new MemoryStream(Settings.InitialContentBufferSize);
        private int ignoreLeadingCrlfOffset = 2;

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>See <see cref="ILineProcessor{T}.ProcessFirstLine"/>.</summary>
        public int ProcessFirstLine(ArraySegment<byte> segment)
        {
            int newOffset = FindNextCrlf(segment.Array, segment.Offset, segment.Count);

            // Due to the way processing is split between the different processors, the first line will always start
            // with one superfluous "\r\n" sequence
            int offset = segment.Offset + this.ignoreLeadingCrlfOffset;
            this.content.Write(segment.Array, offset, newOffset - offset);
            this.ignoreLeadingCrlfOffset = 0;
            return newOffset;
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>Initializes a new instance of the <see cref="UnstructuredContentProcessor"/> class with the
        /// provided <paramref name="encodingTransformation"/>.</summary>
        internal UnstructuredContentProcessor(EncodingTransformation encodingTransformation)
        {
            this.encodingTransformation = encodingTransformation;
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>Gets a stream positioned at the start of the decoded content.</summary>
        /// <param name="rawLength">The raw length of the content in bytes. The length of the returned stream is
        /// guaranteed to not be greater than the raw length.</param>
        /// <remarks>By calling this method, the returned stream is put into the possession of the caller, which is
        /// responsible for properly disposing it.</remarks>
        protected Stream GetContent(out int rawLength)
        {
            rawLength = (int)this.content.Length;
            Stream raw = this.content;
            this.content = null;
            raw.Seek(0, SeekOrigin.Begin);

            switch (this.encodingTransformation)
            {
                case EncodingTransformation.Base64:
                    return new Base64Stream(raw);
                case EncodingTransformation.QuotedPrintable:
                    return new QuotedPrintableStream(raw);
                default:
                    return raw;
            }
        }

        /// <summary>Gets a <see cref="MemoryStream"/> positioned at the start of the decoded content.</summary>
        /// <remarks>By calling this method, the returned stream is put into the possession of the caller, which is
        /// responsible for properly disposing it.</remarks>
        protected MemoryStream GetContent()
        {
            int rawLength;
            Stream contentStream = this.GetContent(out rawLength);
            return StreamHelper.ToMemoryStream(contentStream, Settings.InMemoryStreamBufferSize, rawLength);
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>Returns the offset of the next "\r\n" sequence in <paramref name="buffer"/>.</summary>
        /// <param name="buffer">The buffer to inspect.</param>
        /// <param name="offset">The offset to start at.</param>
        /// <param name="count">The maximum number of bytes to inspect.</param>
        /// <returns>If found, the offset of the next "\r\n" sequence; otherwise <paramref name="offset"/> +
        /// <paramref name="count"/>.</returns>
        /// <exception cref="IndexOutOfRangeException">One or more of the indexes in the range
        /// [<paramref name="offset"/>, <paramref name="offset"/> + <paramref name="count"/>) are not valid indexes
        /// within <paramref name="buffer"/>.</exception>
        /// <exception cref="NullReferenceException"><paramref name="buffer"/> equals <c>null</c>.</exception>
        /// <remarks>
        /// <para>If <paramref name="count"/> > 0 then the returned offset is always greater than
        /// <paramref name="offset"/>, even if there is a "\r\n" sequence at
        /// <paramref name="buffer"/>[<paramref name="offset"/>].</para>
        /// <para>This method assumes that every '\r' character is always followed by a '\n' character.</para>
        /// </remarks>
        private static int FindNextCrlf(byte[] buffer, int offset, int count)
        {
            // - 1 so that the '\n' character after the '\r' is in the buffer as well
            int pastEnd = offset + count - 1;

            for (int index = offset + 1; index < pastEnd; ++index)
            {
                if (buffer[index] == (byte)'\r')
                {
                    return index;
                }
            }

            return offset + count;
        }
    }
}
